#include "../src/FrameReader.hpp"
#include "../src/SyncAlg.hpp"
#include "../src/MKVdecode.hpp"
#include "../src/ImgRebuild.hpp"
#include <functional>
#include <string>
#include <fstream>
#include <fmt/format.h>
#include <thread>
#include <spdlog/spdlog.h>
#include <map>
#include <unistd.h>
#include <list>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

SyncAlg syncalg(5);

class VideoObject
{
  int WriteImageCounter=1;
  public:
    VideoObject(std::filesystem::path inputPath,std::filesystem::path outputPath,std::string UUID,int index_)
    : frame(inputPath/fmt::format("{}.frame",UUID))
    , video(inputPath/fmt::format("{}.mkv",UUID))
    , CameraUUID(UUID)
    , index(index_)
    , imageOutputPath(outputPath)
    {
    }
    virtual ~VideoObject()
    {
      if(GetDataThread.joinable())
        GetDataThread.join();
    }
    int GetIndex() {return index;}
    bool isRuning() {return runFlag;}
    void WriteImage(const ImageType & data)
    {
      imgRebuild.Rebuild((char*)data.data, data.width, data.height, 48),
      stbi_write_jpg(
        (imageOutputPath/fmt::format("{}.jpg",WriteImageCounter)).c_str(),
        data.width,
        data.height,
        3,
        data.data,
        90
      );
      WriteImageCounter++;
    }
    class ManualInput
    {
      ImageType newImage;
      VideoObject* parent;
      public:
        ManualInput(std::string CameraUUID,VideoObject* parent_)
        {
          parent=parent_;
          memcpy(newImage.uuid,CameraUUID.c_str(),CameraUUID.length()+1);
          newImage.width=1920;
          newImage.height=1080;
          newImage.TimeStamp=0;
          newImage.data_size=1920*1080*3;
        }
        void HandleOnce()
        {
          bool InputFlag;
          do
          {
            unsigned char* data=new unsigned char[1920*1080*3];
            newImage.FrameID=parent->frame.GetFrameID();
            parent->video.GetNewImage((char*)data);
            newImage.data=data;
            InputFlag=syncalg.InputImage(newImage,parent->index);
          }
          while(!InputFlag);
        }
    };
    ManualInput GetManualInput()
    {
      return ManualInput(CameraUUID,this);
    }
    void Boot()
    {
      GetDataThread=std::jthread([this](){
        runFlag=true;
        ImageType newImage;
        memcpy(newImage.uuid,CameraUUID.c_str(),CameraUUID.length()+1);
        newImage.width=1920;
        newImage.height=1080;
        newImage.TimeStamp=0;
        newImage.data_size=1920*1080*3;
        while(true)
        {
          unsigned char* data=new unsigned char[1920*1080*3];
          try
          {
            newImage.FrameID=frame.GetFrameID();
            video.GetNewImage((char*)data);
            newImage.data=data;
          }
          catch(const std::exception& e)
          {
            break;
          }
          syncalg.InputImage(newImage,index);
        }
        spdlog::info("Handle Camera {} Thread is exit",CameraUUID);
        runFlag=false;
      });
    }
  private:
    std::filesystem::path imageOutputPath;
    bool runFlag=false;
    int index;
    std::string CameraUUID;
    FrameReader frame;
    MKV_Decode video;
    std::jthread GetDataThread;
    ImgRebuild imgRebuild;
};

std::map<std::string,VideoObject*> UUID2Index;
void Handle_SyncResult(const std::vector<ImageType>& data)
{
  for(const auto & i : data)
  {
    auto ptr=UUID2Index.find(i.uuid);
    if(ptr==UUID2Index.end())
      throw std::runtime_error("Can't Find UUID");
    ptr->second->WriteImage(i);
  }
}

int main()
{
  spdlog::set_level(spdlog::level::debug);
  gst_init(nullptr,nullptr);
  //Read Data
  {
    std::ifstream file("config");
    if(!file.is_open())
      throw std::runtime_error("Can't Open Config");
    std::string InputPath,OutputPath;
    file>>InputPath>>OutputPath;
    for(int i=1;i<=5;i++)
    {
      std::string UUID;
      file>>UUID;
      std::filesystem::path op=OutputPath;
      op=op/fmt::format("{}",i);
      if(!std::filesystem::exists(op))
      {
        if(!std::filesystem::create_directory(op))
          throw std::runtime_error("Can't create directory");
      }
      UUID2Index.insert({UUID,new VideoObject(InputPath,op,UUID,i)});
    }
    if(!file.good())
      throw std::runtime_error("Can't read Config, maybe the format is wrong");
  }

  syncalg.setResultCallback(Handle_SyncResult);

  std::list<VideoObject::ManualInput> manualList;
  for(auto & i : UUID2Index)
    manualList.push_back(i.second->GetManualInput());

  while(true)
  {
    try
    {
      for(auto & i : manualList)
        i.HandleOnce();
    }
    catch(const std::runtime_error& e)
    {
      break;
    }
  }
  
  spdlog::info("Input Data Finish");
  sleep(5);

  for(auto & i : UUID2Index)
    delete i.second;
}
